package db

import (
	"context"
	"errors"
	"log/slog"
	"time"

	"github.com/redis/go-redis/v9"
)

var Redis *RedisClient

type RedisClient struct {
	*redis.Client
}

// 初始化Redis客户端
func InitRedis(ops *redis.Options) {
	client, err := RedisConn(ops)
	if err != nil {
		slog.Error("Redis连接失败", slog.String("error", err.Error()))
	}
	Redis = client
}

// 建立Redis连接
func RedisConn(ops *redis.Options) (*RedisClient, error) {
	var password string
	if len(ops.Password) >= 8 {
		password = ops.Password[0:8] + "--------"
	}
	slog.Info("Redis连接信息", slog.String("host", ops.Addr), slog.String("username", ops.Username), slog.String("password", password), slog.Int("db", ops.DB))

	client := redis.NewClient(ops)
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	_, err := client.Ping(ctx).Result()
	if err != nil {
		return nil, err
	}
	slog.Info("Redis连接成功")

	Redis = &RedisClient{client}
	return &RedisClient{client}, nil
}

// 关闭Redis连接
func CloseRedis() {
	if Redis != nil {
		Redis.Close()
	}
}

// 清空Redis数据库
func (r *RedisClient) FlushDB(key string) (string, error) {
	if r.Client == nil {
		return "", errors.New("未连接Redis")
	}
	ctx := context.Background()
	_, err := r.Client.FlushDB(ctx).Result()
	if err != nil {
		slog.Error("清空Redis库失败", slog.String("error", err.Error()))
		return "", err
	} else {
		slog.Info("清空Redis库成功")
	}
	return "", nil
}

// 测试redis连通性
func (r *RedisClient) Ping() bool {
	return r.Client.Ping(context.Background()).Val() == "PONG"
}

// 写入字符串键值对
func (r *RedisClient) Set(key string, value string, expiration time.Duration) (string, error) {
	if r.Client == nil {
		return "", errors.New("未连接Redis")
	}
	ctx := context.Background()
	res, err := r.Client.Set(ctx, key, value, expiration).Result()
	if err != nil {
		return "", err
	}
	return res, nil
}

// 读取字符串键值对
func (r *RedisClient) Get(key string) (string, error) {
	if r.Client == nil {
		return "", errors.New("未连接Redis")
	}
	ctx := context.Background()
	res, err := r.Client.Get(ctx, key).Result()
	if err != nil {
		// 如果返回的错误是key不存在
		if errors.Is(err, redis.Nil) {
			return "", nil
		}
		return "", err
	}
	return res, nil
}

// 删除字符串键值对
func (r *RedisClient) Del(key string) (int64, error) {
	if r.Client == nil {
		return 0, errors.New("未连接Redis")
	}
	ctx := context.Background()
	res, err := r.Client.Del(ctx, key).Result()
	if err != nil {
		return 0, err
	}
	return res, nil
}

// 写入HASH键值对
func (r *RedisClient) HSet(key string, field string, value string) (int64, error) {
	if r.Client == nil {
		return 0, errors.New("未连接Redis")
	}
	ctx := context.Background()
	res, err := r.Client.HSet(ctx, key, field, value).Result()
	if err != nil {
		return 0, err
	}
	return res, nil
}

// 读取HASH键值对
func (r *RedisClient) HGet(key string, field string) (string, error) {
	if r.Client == nil {
		return "", errors.New("未连接Redis")
	}
	ctx := context.Background()
	res, err := r.Client.HGet(ctx, key, field).Result()
	if err != nil {
		return "", err
	}
	return res, nil
}

// 添加Redis用户, 设置相应ACL权限
func (r *RedisClient) CreateUser(username, password, prefix string) error {
	if r.Client == nil {
		return errors.New("未连接Redis")
	}

	// 设置ACL权限示例
	// client.Do(ctx, "ACL", "SETUSER", "user1", "on", "+@all", "-@dangerous", "~user1:*", ">user1")
	// client.Do(ctx, "ACL", "SETUSER", "user2", "on", "+@all", "-@dangerous", "+get|set|del|expire|ttl|exists", "~user2:*", ">user2")

	// 先删除用户
	_ = r.DeleteUser(username)

	// 设置用户和权限
	ctx := context.Background()
	_, err := r.Client.Do(ctx,
		"ACL",
		"SETUSER",
		username,       // 用户名
		"on",           // 启用用户
		"+@all",        // 允许所有命令
		"-@dangerous",  // 撤销危险命令的访问权限
		"~"+prefix+"*", // 限制用户只能访问特定的键
		">"+password,   // 设置用户密码
	).Result()
	if err != nil {
		return err
	}

	// 持久化 ACL
	_, err = r.Client.Do(ctx, "ACL", "SAVE").Result()
	if err != nil {
		return err
	}

	return nil
}

// DeleteUser 删除 Redis 用户
func (r *RedisClient) DeleteUser(username string) error {
	if r.Client == nil {
		return errors.New("未连接Redis")
	}

	// 删除用户
	ctx := context.Background()
	_, err := r.Client.Do(ctx,
		"ACL",
		"DELUSER",
		username,
	).Result()
	if err != nil {
		return err
	}

	return nil
}

// DeleteUserAndKeys 删除 Redis 用户及其所有键
func (r *RedisClient) DeleteUserAndKeys(username, prefix string) error {
	if r.Client == nil {
		return errors.New("未连接Redis")
	}

	ctx := context.Background()
	// 扫描用户的所有键
	cursor := uint64(0)
	for {
		keys, nextCursor, err := r.Client.Scan(ctx, cursor, prefix+"*", 100).Result()
		if err != nil {
			return err
		}

		// 删除找到的键
		for _, key := range keys {
			_, err := r.Client.Del(ctx, key).Result()
			if err != nil {
				return err
			}
		}

		// 如果光标为 0，表示扫描完成
		if nextCursor == 0 {
			break
		}

		cursor = nextCursor
	}

	// 删除用户
	_, err := r.Client.Do(ctx,
		"ACL",
		"DELUSER",
		username,
	).Result()
	if err != nil {
		return err
	}

	return nil
}

// 统计匹配特定前缀的键的总内存使用
func (r *RedisClient) GetTotalMemoryUsageForPrefix(prefix string) (float64, error) {
	var cursor uint64
	var totalBytes int64
	ctx := context.Background()

	for {
		var keys []string
		var err error

		keys, cursor, err = r.Scan(ctx, cursor, prefix+"*", 10).Result()
		if err != nil {
			return 0, err
		}

		for _, key := range keys {
			bytes, err := r.MemoryUsage(ctx, key).Result()
			if err != nil {
				return 0, err
			}

			totalBytes += bytes
		}

		if cursor == 0 {
			break
		}
	}

	return float64(totalBytes) / 1024.0 / 1024.0, nil
}

// ? ========================== Redis分布式锁 ==========================
const luaReleaseScript = `
    if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
    else
        return 0
    end
`

const luaExtendScript = `
    if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("pexpire", KEYS[1], ARGV[2])
    else
        return 0
    end
`

// 获取分布式锁
func (r *RedisClient) AcquireLock(ctx context.Context, lockKey, lockValue string, expiration time.Duration) (bool, error) {
	ok, err := r.Client.SetNX(ctx, lockKey, lockValue, expiration).Result()
	if err != nil {
		return false, err
	}
	return ok, nil
}

// 释放分布式锁
func (r *RedisClient) ReleaseLock(ctx context.Context, lockKey, lockValue string) error {
	script := redis.NewScript(luaReleaseScript)
	_, err := script.Run(ctx, r.Client, []string{lockKey}, lockValue).Result()
	if err != nil {
		return err
	}
	return nil
}

// 分布式锁续期
func (r *RedisClient) ExtendLock(ctx context.Context, lockKey, lockValue string, expiration time.Duration) (bool, error) {
	script := redis.NewScript(luaExtendScript)
	result, err := script.Run(ctx, r.Client, []string{lockKey}, lockValue, int64(expiration/time.Millisecond)).Int()
	if err != nil {
		return false, err
	}
	return result == 1, nil
}
